from nose.tools import assert_raises
from datetime import date
from webob.multidict import MultiDict as MD
from formosa import ErrorSet, Form, fields, validators

class TestErrorSet(object):
    def setup(self):
        self.errors = ErrorSet()
        self.errors.add('Please enter a value', set(['name']))
        self.errors.add('Please enter a value', set(['age']))
        self.errors.add('Value already taken', set(['uid']))
        self.errors.add('Enter at least one value',
                        set(['opt1', 'opt2', 'opt3']))
        self.errors.add('Something', set(['opt3', 'uid']))
        self.errors.add('Blah', set(['opt3']))
        self.errors.add('Blah', set(['name']))
        self.errors.add('One more thing', set(['age', 'opt2']))
        self.errors.add('Something global')

        self.address_errors = ErrorSet()
        self.address_errors.add('Please enter a value', set(['street1']))
        self.address_errors.add('Please enter a valid state', set(['state']))

        self.errors.add(self.address_errors, set(['address']))

    def test_exceptionhood(self):
        assert isinstance(self.errors, Exception)

    def test_adding(self):
        old_errors = self.errors.errors()
        old_targets = self.errors.targets()
        self.errors.add('Please enter a value', set(['name']))
        assert self.errors.errors() == old_errors
        assert self.errors.targets() == old_targets

    def test_length(self):
        assert len(self.errors) == 10

    def test_targets(self):
        assert self.errors.targets() \
            == set([frozenset(['name']), frozenset(['age']),
                    frozenset(['uid']), frozenset(['opt1', 'opt2', 'opt3']),
                    frozenset(['opt3', 'uid']), frozenset(['opt3']),
                    frozenset(['age', 'opt2']), frozenset(),
                    frozenset(['address'])])

    def test_iter_targets(self):
        assert set(self.errors.iter_targets()) == self.errors.targets()

    def test_targets_for(self):
        assert_raises(KeyError, self.errors.targets_for, 'Not an error')
        assert self.errors.targets_for('Please enter a value') \
            == set([frozenset(['name']), frozenset(['age'])])
        assert self.errors.targets_for('Something global') \
            == set([frozenset()])
        assert self.errors.targets_for('Something') \
            == set([frozenset(['opt3', 'uid'])])

    def test_errors(self):
        assert self.errors.errors() \
            == set(['Please enter a value', 'Value already taken',
                    'Enter at least one value', 'Something', 'Blah',
                    'One more thing', 'Something global', self.address_errors])

    def test_iter_errors(self):
        assert set(self.errors.iter_errors()) == self.errors.errors()

    def test_errors_for(self):
        assert_raises(KeyError, self.errors.errors_for, set(['foofield']))
        assert self.errors.errors_for(set(['opt3'])) == set(['Blah'])
        assert self.errors.errors_for(set(['opt3', 'uid'])) \
            == set(['Something'])
        assert self.errors.errors_for(set(['name'])) \
            == set(['Please enter a value', 'Blah'])

    def test_has_target(self):
        assert self.errors.has_target(set([]))
        assert self.errors.has_target(set(['opt3', 'uid']))
        assert not self.errors.has_target(set(['opt2']))

    def test_has_partial_target(self):
        assert self.errors.has_partial_target(set(['opt1']))
        assert self.errors.has_partial_target(set(['opt1', 'opt2']))
        assert self.errors.has_partial_target(set(['opt1', 'opt3']))
        assert self.errors.has_partial_target(set(['opt2', 'opt3']))
        assert self.errors.has_partial_target(set(['opt1', 'opt2', 'opt3']))

    def test_sorted_targets(self):
        assert self.errors.sorted_targets(['name', 'age', 'uid', 'opt1',
                                           'opt2', 'opt3', 'address']) \
            == [(), ('name',), ('age',), ('uid',), ('opt3',), ('address',),
                ('age', 'opt2'), ('uid', 'opt3'), ('opt1', 'opt2', 'opt3')]
        assert self.errors.sorted_targets(['opt3', 'opt2', 'opt1', 'address',
                                           'name', 'uid', 'age']) \
            == [(), ('opt3',), ('address',), ('name',), ('uid',), ('age',),
                ('opt3', 'uid'), ('opt2', 'age'), ('opt3', 'opt2', 'opt1')]

    def test_str(self):
        assert set(str(self.errors).split('\n')) \
            == set(['* Blah [opt3]', '* Something [opt3, uid]',
                    '* Blah [name]', '* Please enter a value [name]',
                    '* Please enter a value [age]', '* Something global []',
                    '* Enter at least one value [opt1, opt2, opt3]',
                    '* [address]', '  * Please enter a valid state [state]',
                    '  * Please enter a value [street1]',
                    '* Value already taken [uid]',
                    '* One more thing [age, opt2]'])


# XXX Replace this with fields.SubForm once it is written
class ErrorSetField(object):
    def translate(self, input):
        if input.get('foo') != 'bar':
            errors = ErrorSet()
            errors.add('A value is required', set(['city']))
            errors.add('Please choose a valid U.S. state', set(['state']))
            raise errors
        else:
            return 'Aqui'


class TestForm(object):
    def setup(self):
        self.address_child_fields \
            = {'street1': fields.String('street1', False, 30),
               'street2': fields.String('street2', True, 30),
               'city': fields.String('city', False, 20),
               'state': fields.String('state', False, 2, 2),
               'zip': fields.ZipCode('zip')}
        self.address_field = fields.SubForm(self.address_child_fields)
        self.form = Form({'eid': fields.Regex('eid', '^E-[0-9]{4,}$'),
                          'name': fields.String('name', max_length=50),
                          'start_date': fields.Date('start_date'),
                          'opt1': fields.String('opt1', True, max_length=50),
                          'opt2': fields.String('opt2', True, max_length=50),
                          'opt3': fields.String('opt3', True, max_length=50),
                          'address': self.address_field,
                          'notes': fields.String('notes', True,
                                                 max_length=1000)},
                         [validators.AtLeast(1, ('opt1', 'opt2', 'opt3'))])

    def test_invalid_input(self):
        try:
            self.form.translate(MD(eid='E-123', name='   ',
                                   start_date='8/10/2007'))
        except ErrorSet, e:
            assert e.targets() == set([frozenset(['name']), frozenset(['eid']),
                                       frozenset(['opt1', 'opt3', 'opt2']),
                                       frozenset(['address'])])
        else:
            assert False, 'Excepted errors'

    def test_valid_input(self):
        input = self.form.translate(MD(eid='E-1234', name='  Foo',
                                       start_date='8/10/2007', opt1='testing!',
                                       other='foobar', xyzzy='foofoo',
                                       street1='123 Test St.', city='Testville',
                                       state='MN', zip='56303'))
        assert input == {'eid': 'E-1234', 'name': 'Foo', 'notes': None,
                         'start_date': date(2007, 8, 10), 'opt1': 'testing!',
                         'opt2': None, 'opt3': None,
                         'address': {'street1': '123 Test St.', 'street2': None,
                                     'state': 'MN', 'zip': '56303',
                                     'city': 'Testville'}}
